package com.pureland.boot.exception;


import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.netflix.client.ClientException;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import com.pureland.common.entity.R;
import com.pureland.common.entity.enums.CommonError;
import com.pureland.common.exception.CheckedException;
import com.pureland.security.exceptions.*;
import feign.RetryableException;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.exceptions.PersistenceException;
import org.mybatis.spring.MyBatisSystemException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.yaml.snakeyaml.constructor.DuplicateKeyException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.UnexpectedTypeException;
import javax.validation.ValidationException;
import java.sql.SQLSyntaxErrorException;
import java.util.List;


/**
 * @projectName: pureland_cloud
 * @className: com.pureland.boot.config.exception.GlobalExceptionHandler
 * @description: 全局异常处理器
 * @author: tong.li
 * @createTime: 2020/10/11 15:38
 * @version: v1.0
 * @copyright: 版权所有 李彤 © 2020
 */
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired(required = false)
    private DiscoveryClient discoveryClient;


    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ResponseEntity<R<ResponseEntity<Void>>> jsonErrorHandler(Exception e) {
        HttpStatus defaultErrorResult = HttpStatus.OK;
        logger.error("[堆栈日志]", e);
        if (e instanceof CheckedException) {
            CheckedException exception = (CheckedException) e;
            return new ResponseEntity<>(R.fail(exception.getCode(), exception.getLocalizedMessage()), defaultErrorResult);
        } else if (e instanceof IllegalArgumentException) {
            IllegalArgumentException exception = (IllegalArgumentException) e;
            return new ResponseEntity<>(R.fail(exception.getLocalizedMessage()), defaultErrorResult);
        } else if (e instanceof MultipartException) {
            return new ResponseEntity<>(R.fail("文件过大,请控制文件大小"), defaultErrorResult);
        }
        else if (e instanceof InternalAuthenticationServiceException) {
            InternalAuthenticationServiceException exception = (InternalAuthenticationServiceException) e;
            if (exception.getCause() instanceof SQLSyntaxErrorException) {
                return new ResponseEntity<>(R.fail(exception.getCause().getLocalizedMessage()), defaultErrorResult);
            }
            return new ResponseEntity<>(R.fail(exception.getLocalizedMessage()), defaultErrorResult);
        }
        else if (e instanceof RuntimeException) {
            logger.warn("[RuntimeException]", e);
            RuntimeException exception = (RuntimeException) e;
            return new ResponseEntity<>(R.fail(exception.getLocalizedMessage()), defaultErrorResult);
        }
        return new ResponseEntity<>(R.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()), defaultErrorResult);
    }

    @ExceptionHandler(UnexpectedTypeException.class)
    @ResponseBody
    public final R<ResponseEntity<Void>> unexpectedTypeException(UnexpectedTypeException e) {
        return R.fail(e.getLocalizedMessage());
    }

    private static final String DISABLED_TEXT = "User is disabled";


    @ExceptionHandler({OAuth2Exception.class})
    @ResponseBody
    public final ResponseEntity<R> oAuth2Exception(OAuth2Exception e) {
        logger.error("[错误内容]", e);
        final Throwable throwable = e.getCause();
        HttpStatus httpStatus = null;
        if (e instanceof InvalidGrantException) {
            logger.warn("[error message] - [{}]", e.getLocalizedMessage());
            InvalidGrantException exception = (InvalidGrantException) e;
            if (exception.getLocalizedMessage().contains(DISABLED_TEXT)) {
                return new ResponseEntity<>(R.fail(e.getHttpErrorCode(), "用户已禁用"), HttpStatus.OK);
            }
            return new ResponseEntity<>(R.fail(e.getHttpErrorCode(), "账号或密码错误"), HttpStatus.OK);
        }
        else if (throwable instanceof InvalidException) {
            httpStatus = HttpStatus.UPGRADE_REQUIRED;
        } else if (throwable instanceof ServerErrorException) {
            httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
        } else if (throwable instanceof ForbiddenException) {
            httpStatus = HttpStatus.FORBIDDEN;
        } else if (throwable instanceof UnauthorizedException) {
            httpStatus = HttpStatus.UNAUTHORIZED;
        } else if (throwable instanceof MethodNotAllowedException) {
            httpStatus = HttpStatus.METHOD_NOT_ALLOWED;
        }
        return new ResponseEntity<>(R.fail(e.getHttpErrorCode(), e.getLocalizedMessage()), HttpStatus.OK);
    }

    @ExceptionHandler({AuthenticationException.class})
    @ResponseBody
    public final ResponseEntity<R> authException(AuthenticationException e) {
        logger.error("[错误内容]", e);
        final Throwable throwable = e.getCause();
        HttpStatus httpStatus = null;
        if (throwable instanceof InvalidException) {
            httpStatus = HttpStatus.UPGRADE_REQUIRED;
        } else if (throwable instanceof ServerErrorException) {
            httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
        } else if (throwable instanceof ForbiddenException) {
            httpStatus = HttpStatus.FORBIDDEN;
        } else if (throwable instanceof UnauthorizedException) {
            httpStatus = HttpStatus.UNAUTHORIZED;
        } else if (throwable instanceof MethodNotAllowedException) {
            httpStatus = HttpStatus.METHOD_NOT_ALLOWED;
        }
        return new ResponseEntity<>(R.fail(e.getMessage(), e.getLocalizedMessage()), HttpStatus.OK);
    }


    @ExceptionHandler(AccessDeniedException.class)
    @ResponseBody
    public final R<ResponseEntity<Void>> accessDeniedException(AccessDeniedException e, HttpServletRequest request) {
        String method = request.getMethod();
        String uri = request.getRequestURI();
        logger.warn("\n[================================================================]\n" +
                "[异常信息] - [{}]\n" +
                "[请求地址] - [{}] - [{}]\n" +
                "[返回消息] - [{}]\n" +
                "[================================================================]", e.getLocalizedMessage(), method, uri, CommonError.ACCESS_DENIED.desc());
        return R.fail(CommonError.ACCESS_DENIED);
    }


    @ExceptionHandler(InsufficientAuthenticationException.class)
    @ResponseBody
    public final R<ResponseEntity<Void>> insufficientAuthenticationException(InsufficientAuthenticationException e) {
        return R.fail(e.getLocalizedMessage());
    }

    @ExceptionHandler(DuplicateKeyException.class)
    @ResponseBody
    public final R<ResponseEntity<Void>> duplicateKeyException(DuplicateKeyException e) {
        logger.error("[主键冲突]", e);
        return R.fail("数据主键冲突");
    }


    @ExceptionHandler(MyBatisSystemException.class)
    @ResponseBody
    public final R<ResponseEntity<Void>> myBatisSystemException(MyBatisSystemException e) {
        logger.error("[Mybatis 系统异常]", e);
        if (e.getCause() instanceof PersistenceException) {
            if (e.getCause().getCause() instanceof MybatisPlusException) {
                if (e.getCause().getCause().getCause() instanceof SQLSyntaxErrorException) {
                    SQLSyntaxErrorException sqlSyntaxErrorException = (SQLSyntaxErrorException) e.getCause().getCause().getCause();
                    return R.fail("SQL 异常,错误信息为 " + sqlSyntaxErrorException.getLocalizedMessage());
                }
                return R.fail(e.getLocalizedMessage());
            }
            return R.fail(e.getLocalizedMessage());
        }
        return R.fail(e.getLocalizedMessage());
    }

    @ExceptionHandler(BadSqlGrammarException.class)
    @ResponseBody
    public final R<ResponseEntity<Void>> badSqlGrammarException(BadSqlGrammarException e) {
        logger.error("[Mybatis SQL 异常]", e);
        if (e.getCause() instanceof SQLSyntaxErrorException) {
            SQLSyntaxErrorException sqlSyntaxErrorException = (SQLSyntaxErrorException) e.getCause();
            return R.fail("Mybatis SQL绑定异常,错误信息为 " + sqlSyntaxErrorException.getLocalizedMessage());
        }
        return R.fail(e.getLocalizedMessage());
    }

    @ExceptionHandler(MybatisPlusException.class)
    @ResponseBody
    public final R<ResponseEntity<Void>> mybatisPlusException(MybatisPlusException e) {
        logger.error(e.getCause().getClass().toString(),e);
        return R.fail(e.getLocalizedMessage());
    }

    @ExceptionHandler(HystrixRuntimeException.class)
    @ResponseBody
    public final R<ResponseEntity<Void>> hystrixRuntimeException(HystrixRuntimeException e) {
        if (e.getCause() instanceof RuntimeException) {
            RuntimeException runtimeException = (RuntimeException) e.getCause();
            if (runtimeException.getCause() instanceof ClientException) {
                ClientException clientException = (ClientException) runtimeException.getCause();
                List<String> services = discoveryClient.getServices();
                for (String serviceId : services) {
                    String message = clientException.getLocalizedMessage();
                    if (StringUtils.contains(message, serviceId)) {
                        logger.warn("[服务访问失败] - [{}] - [错误信息] - [{}]", serviceId, message);
                        return R.fail(CommonError.INNER_SERVICE_ERROR);
                    }
                }
            }
        }
        logger.warn("[服务调用失败] - [{}]", e.getLocalizedMessage());
        return R.fail("服务响应失败，请稍后再试");
    }

    @ExceptionHandler(ClientException.class)
    @ResponseBody
    public final R<ResponseEntity<Void>> clientException(ClientException e) {
        logger.error("内部服务响应失败", e);
        return R.fail(CommonError.INNER_SERVICE_ERROR);
    }


    @ExceptionHandler(ExcelAnalysisException.class)
    @ResponseBody
    public final R<ResponseEntity<Void>> excelAnalysisException(ExcelAnalysisException e) {
        logger.error("Excel 解析失败", e);
        if (e.getCause() instanceof CheckedException) {
            CheckedException exception = (CheckedException) e.getCause();
            return R.fail(exception.getLocalizedMessage());
        } else if (e.getCause() instanceof ExcelDataConvertException) {
            ExcelDataConvertException exception = (ExcelDataConvertException) e.getCause();
            return R.fail("导入失败,类型转换错误,请检查内容或者模板是否正确");
        }
        return R.fail(e.getLocalizedMessage());
    }


    @ExceptionHandler(RetryableException.class)
    @ResponseBody
    public final R<ResponseEntity<Void>> retryableException(RetryableException e) {
        logger.error("重试失败", e);
        return R.fail(CommonError.INNER_SERVICE_ERROR);
    }


    @ExceptionHandler(ValidationException.class)
    @ResponseBody
    public final R<ResponseEntity<Void>> handlerValidationException(final Exception e) {
        ValidationException exception = (ValidationException) e;
        if (exception.getCause() instanceof CheckedException) {
            return R.fail(CommonError.REQUEST_PARAM_ERROR.type(), exception.getCause().getLocalizedMessage());
        }
        return R.fail(CommonError.REQUEST_PARAM_ERROR.type(), exception.getLocalizedMessage());
    }

    /**
     * 通用的接口映射异常处理方法
     */
    @Override
    protected @ResponseBody
    ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers,
                                                   HttpStatus status, WebRequest request) {

        if (ex instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException e= (MethodArgumentNotValidException) ex;
            logger.error("MethodArgumentNotValidException 报错内容",e);
            String message = appendAllFieldError(
                    (e).getBindingResult().getFieldErrors(), ex);
            logger.warn("[参数验证错误] - [{}]", message);
            return new ResponseEntity<>(R.fail(CommonError.REQUEST_PARAM_ERROR.type(), message), HttpStatus.OK);
        }else if (ex instanceof BindException) {
            BindException e= (BindException) ex;
            logger.error("BindException 报错内容",e);
            return new ResponseEntity<>(R.fail(CommonError.REQUEST_PARAM_ERROR.type(), appendAllFieldError((e).getBindingResult().getFieldErrors(), ex)), HttpStatus.OK);
        }else if(ex instanceof HttpMessageNotReadableException){
            HttpMessageNotReadableException e= (HttpMessageNotReadableException) ex;
            logger.error("HttpMessageNotReadableException 报错内容",e);
            return new ResponseEntity<>(R.fail(CommonError.REQUEST_PARAM_ERROR.type(), "请求参数错误,请检查参数格式或参数类型"), HttpStatus.OK);
        }else if(ex instanceof HttpRequestMethodNotSupportedException){
            HttpRequestMethodNotSupportedException e= (HttpRequestMethodNotSupportedException) ex;
            logger.error("HttpRequestMethodNotSupportedException 报错内容",e);
            return new ResponseEntity<>(R.fail(CommonError.REQUEST_PARAM_ERROR.type(), ex.getLocalizedMessage()), HttpStatus.OK);
        } else if (ex instanceof MethodArgumentTypeMismatchException) {
            MethodArgumentTypeMismatchException exception = (MethodArgumentTypeMismatchException) ex;
            logger.error("参数转换失败，方法:{} ，参数：{}"
                    + ",异常信息: {}", exception.getParameter().getMethod().getName(),exception.getName(),exception.getLocalizedMessage());
            if (ex.getCause() instanceof ConversionFailedException &&
                    ex.getCause().getCause() instanceof IllegalArgumentException) {
                IllegalArgumentException e= (IllegalArgumentException) ex;
                logger.error("IllegalArgumentException 报错内容",e);
                return new ResponseEntity<>(R.fail(ex.getCause().getCause().getLocalizedMessage()), HttpStatus.OK);
            }
        }
        //未处理异常
        final String contextPath = request.getContextPath();
        logger.error("执行请求失败 - [{}]", contextPath);
        return new ResponseEntity<>(R.fail(ex.getLocalizedMessage()), HttpStatus.OK);
    }


    /**
     * 拼接所有错误字段信息
     * @author 李彤
     * @param fieldErrors  所有错误字段实体
     * @param exception 异常
     * @return java.lang.String
     * @date 2019-06-13
     */
    private String appendAllFieldError(List<FieldError> fieldErrors, Exception exception){
        StringBuilder allFieldErrorMsg = new StringBuilder();
        if (!ObjectUtils.isEmpty(fieldErrors)) {
            for (FieldError fieldError : fieldErrors) {
                allFieldErrorMsg.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append("; ");
            }
        }
        logger.warn("occur handled exception: {}, errorMsg: {}", exception.getClass().getName(), allFieldErrorMsg.toString());
        return allFieldErrorMsg.toString();
    }
}